#include "notifyADT.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>



static char* concatPath(char* pathDir, char* pathSubDir);
/*
funcion concatPath concatena dos string agregandole en el medio un '/'
retorna NULL en caso de error.
*/

void freeWatch(watchD* wd);
/*
funcion freeWatch libera la memoria toda la memoria de la estructura
watchD
*/

static int isDotDir(char* path);
/*
devuelve 0 si el path No es "." ni "..", 1 caso contrario.
*/

listElementT getWatchPath(listADT l, int wd);


static int
compareByWd(listElementT e1, listElementT e2)
{
    if(((watchPath*)e1)->wd == ((watchPath*)e2)->wd)
        return 1;
    return 0;
}

static int
compareByName_n(listElementT e1, listElementT e2)
{
    int len;
    len = strlen(((watchPath*)e2)->path);

    if(strncmp(((watchPath*)e2)->path, ((watchPath*)e1)->path, len) == 0)
        return 1;

    return 0;
}

static int
compareByName(listElementT e1, listElementT e2)
{
    if(strcmp(((watchPath*)e2)->path, ((watchPath*)e1)->path) == 0)
        return 1;

    return 0;
}

static void
freeElement(listElementT elem)
{
    free(((watchPath*)elem)->path);

    return;
}

int
setWatchs(char* path, watchD* wd)
{

    if (path == NULL || wd == NULL)
        return -1;

    if((isDir(path)) != 1)
    {
        fprintf(stderr, "%s is not a dir or it doesn't exists\n", path);
        return -1;
    }

    if(wd->waList == NULL)
    {
        wd->waList = NewList(compareByWd, freeElement, sizeof(watchPath));
        if(wd->waList == NULL)
            return -1;
    }

    if(addWatchs(wd, path) == -1)
    {
        freeWatch(wd);
        return -1;
        fprintf(stderr, "Error with addWatchs using %s as path\n", path);
    }
    return 0;
}

int
addWatchs(watchD* wd, char* pathRoot)
{
    DIR *dip;
    int lenPath;
    char *aux;
    struct dirent* dit;
    watchPath watchPoint;

    if((dip = opendir(pathRoot)) == NULL)
        return -1;

    /* seteo un watch point sobre pathRoot */
    if ((watchPoint.wd = inotify_add_watch(wd->fd, pathRoot, MASK_EVENTS)) < 0)
        return -1;

//   se podria hacer un strdup
    lenPath = strlen(pathRoot);
    if((watchPoint.path = calloc(lenPath+1, sizeof(char))) == NULL)
        return -1;

    strcpy(watchPoint.path, pathRoot);

    /* el watch point sobre pathroot se guarda en la lista */
    Insert(wd->waList, &watchPoint);

    while((dit = readdir(dip)) != NULL)
    {
        if(dit->d_type == DT_DIR && !isDotDir(dit->d_name))
        {
            /*
            creo el path de un subdirectorio de pathRoot
            y llamo recursivamente la funcion addWatchs
            */
            aux = concatPath(pathRoot, dit->d_name);
            if(aux == NULL)
            {
                free(watchPoint.path);
                return -1;
            }

            if(addWatchs(wd, aux) == -1)
            {
                free(watchPoint.path);
                return -1;
            }
        }
    }
    /* REVISAR SI SE PUEDE LIBERAR watchPoint.path  porque Insert hace un shallow cpy*/
    closedir(dip);
    return 0;
}

static int
isDotDir(char* path)
{
    return (strcmp(path, "..") == 0) || (strcmp(path, ".") == 0);
}

static char*
concatPath(char* pathDir, char* pathSubDir)
{
    int len, lenDir;
    char* p;

    lenDir = strlen(pathDir);
    len = lenDir + strlen(pathSubDir);
    p = calloc(len+2, sizeof(char));
    //  sumo 2 por el null terminated y '/'
    if(p == NULL)
        return NULL;

    if(strcpy(p, pathDir) == NULL)
        return NULL;

    if(p[lenDir-1] != '/')
    {
        if(strcat(p, "/") == NULL)
            return NULL;
    }

    if(strcat(p, pathSubDir) == NULL)
        return NULL;

    return p;
}

int
notify(void)
{
    return inotify_init();
}

int
update(watchD *wd, struct inotify_event* event)
{
    int eventWd, resp;
    watchPath *wp;
    char *auxPath;
    /* busco sobre la lista de watchpoints el
    watch descriptor que provoco el evento */
    eventWd = event->wd;

    if((event->mask == (IN_DELETE | IN_ISDIR)) ||
        (event->mask == (IN_MOVED_FROM | IN_ISDIR)))
    {

        if ((wp = (watchPath*)getWatchPath(wd->waList, eventWd)) == NULL)
        {
            return -1;
        }


        auxPath = concatPath(wp->path, event->name);
        if(auxPath == NULL)
        {
            free(wp);
            return -1;
        }

        if((resp = remWatch(wd, auxPath)) == -1)
        {
            free(auxPath);
            free(wp);
            return -1;
        }

        free(wp);
        free(auxPath);
        return resp;
    }

    if((event->mask == (IN_CREATE | IN_ISDIR))  ||
        (event->mask == (IN_MODIFY | IN_ISDIR)) ||
        (event->mask == (IN_MOVED_TO | IN_ISDIR)))
    {

        if ((wp = (watchPath*)getWatchPath(wd->waList, eventWd)) == NULL)
            return -1;

        if((resp = addWatchs(wd, wp->path)) == -1)
        {
            free(wp);
            return -1;
        }

        free(wp);
        return resp;
    }

    return 1;
}

char *
getWatchCompletePath(struct inotify_event *e, watchD *w)
{
    listElementT ret;
    char *retStr = NULL;
    int size = 0;

    if (e == NULL || w == NULL)
        return NULL;

    if ((ret = getWatchPath(w->waList, e->wd)) == NULL)
        return NULL;

    size = strlen(((watchPath *)ret)->path) + 1;
    if ((retStr = calloc(size, 1)) == NULL)
        return NULL;

    sprintf(retStr, "%s", ((watchPath *)ret)->path);

    free(ret);

    return retStr;
}

listElementT
getWatchPath(listADT l, int wd)
{
    watchPath aux;
    aux.wd = wd;
    return GetDatoByComp(l, &aux);
}

void
freeWatch(watchD* wd)
{
    if (wd == NULL)
        return;

    FreeList(wd->waList);


    return;
}

int
remWatch(watchD *wd, char* path)
{
    watchPath wp, *auxWp;
    char *aux;

    if (wd == NULL || path == NULL)
        return -1;

    /*
    cambio la funcion de comparacion de la lista,
    ahora compara por nombre de path
    */
    ChangeCompareFunc(wd->waList, compareByName);
    wp.path = strdup(path);

    /*
    borro de la lista el watchpoint
    */
    auxWp = (watchPath*)GetDatoByComp(wd->waList, (listElementT)(&wp));
    if(auxWp == NULL)
    {
        free(wp.path);
        return -1;
    }
    Delete(wd->waList, (listElementT)auxWp);
    if(inotify_rm_watch(wd->fd, auxWp->wd) == -1)
    {
        free(auxWp);
        return -1;
    }
    /*
    hago un read porque inotify_rm_watch
    genera un evento de con mask IN_IGNORED
    */
//    read(wd->fd, buf, BUF_LEN_NOTIF);


    /*
    verifico si path tiene subdirectorios, caso afirmativo,
    'V'orro tambien los watchpoint correspondientes usando compareByName_n
    */
    ChangeCompareFunc(wd->waList, compareByName_n);
    if((aux = realloc(wp.path, sizeof(char)*(strlen(wp.path)+2))) == NULL)
    {
        free(wp.path);
        return -1;
    }

    wp.path = aux;
    strcat(wp.path, "/");

    while((auxWp = (watchPath*)GetDatoByComp(wd->waList, (listElementT)(&wp))) != NULL)
    {
        Delete(wd->waList, (listElementT)auxWp);
        if(inotify_rm_watch(wd->fd, auxWp->wd) == -1)
        {
            free(auxWp);
            return -1;
        }
        /*
        hago un read porque inotify_rm_watch
        genera un evento de con mask IN_IGNORED
        */
//        read(wd->fd, buf, BUF_LEN_NOTIF);
    }

    ChangeCompareFunc(wd->waList, compareByWd);
    return 1;
}
